iT邦幫忙

2026 iThome 鐵人賽

DAY 23
1

就像偵探辦案不會把嫌疑名單上每一個人都問一輪口供,而是先靠案發現場的蛛絲馬跡鎖定範圍再仔細盤問——/ask-vault 也不通讀全庫正文,先靠標題與標籤篩出候選筆記,再只對候選筆記做仔細的語意理解,過程中也修正了兩個規格與實際行為對不上的地方。

brain-cli 現有的 scan/health/graph 都只能做結構化查詢:列出筆記清單、健康度統計、Wikilink 圖的節點與邊。這些指令回答不了「我之前寫過哪些關於 Cobra 的筆記?」這種自然語言問題——它們不理解語意,只理解欄位。但反過來,若每次提問都請 Agent 把 vault 裡所有筆記的正文讀過一遍才回答,vault 筆記量一旦成長,這個做法既慢又貴,而且大部分被讀到的筆記其實跟問題無關。今天要定案的 /ask-vault,就是在這兩個極端之間找一條路:先用 brain-cli 既有的結構化索引快速篩出候選筆記,再由 Claude Code 只讀候選筆記的正文做語意理解。

兩階段檢索:結構先篩,語意後讀

/ask-vault <自然語言問題> 的核心流程分兩段:

  1. 結構檢索:執行 brain scan --json 取得全庫筆記的 id/title/type/status/path,用問題關鍵字比對 title 與 Frontmatter tags 篩出候選清單,這一階段完全不讀正文
  2. 語意理解:只針對候選清單逐篇讀取正文全文,理解內容後統整答案;候選清單以外的筆記,這次提問不讀取其正文。

這個切分延續 Day18(agent-tool-bridge)「Agent 透過既有唯讀 --json 指令取得結構化資料」的模式,也延續 Day15-17「斜線指令定義檔 + Agent 依步驟直接執行,不新增 Go 子指令」的做法——/ask-vault 完全建立在既有的 brain scan/brain graph 之上,沒有新增、修改任何 brain-cli 的子指令、旗標或輸出 schema。

寫規格時漏算的一件事:scan --json 沒有 tags

規格草稿原本寫「依 scan --jsontitle/tags 欄位比對」,寫完 ask-vault.md 準備實際驗證時才發現:翻開 cmd/brain/main.goscanJSONNote 結構,欄位只有 id/title/type/status/pathtags 從來沒有輸出過。

type scanJSONNote struct {
	ID     string `json:"id"`
	Title  string `json:"title"`
	Type   string `json:"type"`
	Status string `json:"status"`
	Path   string `json:"path"`
}

Non-Goal 明確寫著「不新增、不修改任何 brain-cli Go 子指令、旗標或輸出 schema」,所以解法不是回頭幫 scan --json 加欄位,而是換一個不違反 Non-Goal 的路徑:scan --json 已經給了每篇筆記的 path,Agent 可以自己依 path 讀取檔案開頭以 --- 分隔的 YAML Frontmatter 區塊取得 tags——只讀到第二個 --- 為止,不讀之後的正文。這仍然算「結構檢索」,因為 Frontmatter 跟正文本來就是用 --- 明確分隔的兩塊資料,讀 Frontmatter 不等於讀正文全文。發現這個落差後回頭同步修正了 design.mdspecs/qa-retrieval/spec.md 的用字,避免規格文件跟實際行為對不上。

候選為零時:圖鄰居擴散,但不能拿全庫節點硬湊

如果關鍵字在 title/tags 都比對不到,直接回報「找不到相關筆記」太快放棄——brain graph --json 手上還有一份 Wikilink 的圖,值得再試一次。第一版寫法是:「關鍵字對不到任何節點的 label 時,就拿全庫所有節點當起點去擴散鄰居」。這句話乍看合理,實測後發現是個陷阱:只要圖裡還有任何一條邊,這個規則永遠會湊出同一批「有邊的節點」當候選,不管問題內容是什麼——因為「全庫所有節點的鄰居聯集」本來就是圖裡所有非孤立節點,跟關鍵字完全無關。這樣一來,規格要求的「擴散一層後仍找不到候選」這個分支永遠不會發生,除非整張圖沒有任何一條邊,等於這條防線形同虛設。

修正後的規則是:擴散前一定要先找到「種子節點」——關鍵字要嘛寬鬆比對命中某個節點的 label,要嘛命中該筆記 Frontmatter 裡「第一階段沒用到」的 aliases 欄位(讀取範圍一樣只到 Frontmatter,不算讀正文)。兩者都比對不到,就直接判定擴散無以為繼,回報找不到相關筆記,不會退而求其次拿全庫節點硬湊。有種子節點才收集它在 edges 裡的直接鄰居當新候選。這樣「擴散後仍為零」才是一個真的會發生、而不是理論上存在的分支。

候選過多時:排序取前段,但誠實揭露限縮

候選數量的另一端問題是「太多」。這裡刻意不寫死一個絕對數字上限(例如「最多 5 篇」),而是要求 Agent 依「問題關鍵字命中 title/tags 的次數」排序後,自行判斷讀取正文的合理範圍——但必須在最終回答裡告訴使用者「本次已限縮候選範圍,只根據其中 N 篇筆記作答」,並附上限縮前的候選總數。理由是 vault 規模差異很大,寫死數字要嘛在小 vault 上過度限縮、要嘛在大 vault 上仍然太多;真正該規範的是「限縮這件事必須對使用者透明」,而不是規定死板的門檻。

實際跑一遍:demo vault 上的三個情境

obsidian-agent-brain 的 demo vault(14 篇筆記、14 條 Wikilink 邊,跟 Day21 定案的圖一致)實際跑三個情境:

情境一:關鍵字直接命中 title ——問「vault 裡有哪些跟 Cobra 這個 CLI 框架相關的筆記?」,關鍵字「Cobra」直接命中 4 篇筆記的 title(《Cobra CLI 框架》《選用 Cobra 作為 CLI 框架》《Cobra flag 綁定筆記》《Cobra 子指令樹筆記》),第一階段完全沒讀正文。第二階段讀完這 4 篇正文後,答案能具體說出《選用 Cobra 作為 CLI 框架》是一篇 ADR、記錄了選用 spf13/cobra 而非手刻 flag 套件的決策背景,其餘三篇分別是骨架用法、flag 綁定、子指令樹的參考筆記,並附上這 4 篇的標題作為引用清單。

情境二:關鍵字命中不到 title/tags,靠 aliases 觸發圖鄰居擴散 ——問「vault 裡有沒有筆記提到 spf13/cobra 這個套件?」,關鍵字「spf13」在所有筆記的 title/tags 都比對不到,第一階段候選為零。第二階段(擴散)發現「Cobra CLI 框架」這篇筆記的 Frontmatter aliases 正好是 ["spf13/cobra"],命中種子節點,沿 Wikilink 圖擴散一層拿到其餘 3 篇 Cobra 筆記,候選清單變成同一組 4 篇。讀完正文後確認《Cobra CLI 框架》與《選用 Cobra 作為 CLI 框架》兩篇正文都明確提到 github.com/spf13/cobra,回答時明確揭露「標題/標籤未直接命中關鍵字,已沿 Wikilink 圖擴散一層鄰居筆記後才找到候選」。

情境三:完全找不到相關筆記 ——問「vault 裡有沒有討論量子糾纏演算法的筆記?」,關鍵字對所有筆記的 title/tags/label/aliases 都比對不到,擴散無種子可用,直接回報「vault 中找不到相關筆記」,不進入第二階段,也沒有勉強拼湊答案。

demo vault 規模不足以自然湊出「候選過多」的情境(title 關鍵字比對最多也只命中 4 篇),所以另外用一個假設情境驗證限縮規則:若 vault 成長到有 12 篇筆記的 tags 都含 golang,問「vault 裡跟 golang 有關的筆記都在講什麼?」會篩出 12 篇候選,超過可合理逐篇讀取的範圍;此時依「關鍵字命中 title/tags 的次數」排序,只取排序在前的 5 篇進入語意理解,回答裡明確寫出「本次已限縮候選範圍,原本有 12 篇候選,只根據其中 5 篇作答」。這個情境不是真的在 demo vault 上跑出來的,特別標註是假設數據,用來說明規則本身怎麼運作。

這篇還沒做的事:留給 Day24

/ask-vault 目前的來源揭露只到「列出本次回答實際引用到的筆記標題」,沒有做到逐句可追溯的嚴格引用格式,也沒有防幻覺的強制校驗機制——沒有東西能保證 Agent 統整答案時,真的每一句話都能對應回某篇候選筆記的具體段落。這是刻意的階段切分:今天先把「結構檢索 → 語意理解」這條兩階段流程與候選調整規則定案,Day24 才會在這個基礎上疊加更嚴格的引用格式要求與防幻覺機制,讓 qa-retrieval 從「檢索流程可用」進化到「回答品質可信」。


上一篇
圖譜驅動的知識發現:尋找知識樞紐 (Hubs) 與關聯斷點
下一篇
防禦 AI 幻覺 (Hallucination):基於 Vault 原生筆記的引用驗證
系列文
打造 AI Agent 驅動的第二大腦:用 Go + Claude Code + Obsidian + Graphify 打造工程師知識作業系統28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言